/*
Kakine
ActionScript Framework
Copyright © 2011, PUNY, LLC.

This file is part of KAKINE.

KAKINE is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

KAKINE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with KAKINE.  If not, see <http://www.gnu.org/licenses/>.
*/ 
package com.puny.utils
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.geom.Point;
	
	/**
	 * @private
	 */
	public class ClosedCurveDetector extends EventDispatcher
	{
		public static const EXTRACTED:String = "extracted";

		private var _points:Array = new Array();
		private var _loops:Array = new Array();
		private var _id:uint = 0;
		
		private var _last1:Point = null;
		private var _last2:Point = null;
		private var _first:Point = null;
		
		private var _accumulatedDistance:Number = 0;
		private var _accumulatedPoint:Number = 0;
		private var _accumulatedAngle:Number = 0;
		private var _pointsAverage:Number = 0;
		
		private var _extracted:Boolean = false;
		
		private var _closedRate:Number = 10;
		private var _threshold:Number = 5;
		private var _minLength:Number = 10;
		
		public function ClosedCurveDetector(id:int) {
			initDetector();
			_id = id;
		}
		public function get id():uint {
			return _id;
		}
		public function initDetector():void {
			_points = new Array();
			_loops = new Array();
			
			_last1 = null;
			_last2 = null;
			_first = null;
			_extracted = false;
			
			_accumulatedDistance = 0;
			_accumulatedPoint = 0;
			_accumulatedAngle = 0;
			_pointsAverage = 0;
			//trace("detector was initialized");
		}
		public function foundLoops():uint {
			return _loops.length;
		}
		public function set closedRate(value:Number):void {
			//trace("closed rate:" + value);
			_closedRate = value;
		}
		public function set threshold(value:Number):void {
			//trace("threshold:" + value);
			_threshold = value;
		}
		public function set minLength(value:Number):void {
			//trace("minLength:" + value);
			_minLength = value;
		}
		public function get learningLevel():int {
			return foundLoops() * 100 / (_threshold+1);
		}
		private function getAngle(pt:Point):Number {
				var a:Point = _last1.subtract(_last2);
				var b:Point = pt.subtract(_last1);
				var inner:Number = a.x * b.x + a.y * b.y;
				var len1:Number = Math.sqrt(a.x * a.x + a.y * a.y);
				var len2:Number = Math.sqrt(b.x * b.x + b.y * b.y);
				
				var d:Number = (a.x*b.y - a.y*b.x) >= 0 ? 1 : -1;
				if ( len1 != 0 && len2 != 0 ) {
					var cos:Number = inner / len1 / len2;
					if ( cos > 1 || cos < -1) {
						return 0;
					} else {
						return d * Math.acos(cos);
					}
				}
				return 0;			
		}
		private function resetLoop(pt:Point):void {
			//trace("reset loop");
			_first = pt;
			_accumulatedDistance = 0;
			_accumulatedAngle = 0;
		}
		public function addPoint(pt:Point):Number {
			var theata:Number = 0;
			if (_last1 != null && _last2 != null) {
				theata = getAngle(pt);
				_accumulatedAngle += theata;
				_accumulatedDistance += Point.distance(pt,_last1);

				if (_accumulatedAngle > 2 * Math.PI || _accumulatedAngle < -2*Math.PI) {
					//trace("_acculuated Angle=" + _accumulatedAngle);
					if (_first != null) {
						//To figure out Summary
						var distance:Number = Point.distance(_first,pt);
						var rate:Number = (distance * 100 / _accumulatedDistance);
						//trace("  Loop Summary:closed rate = " + rate + ":length = " + _accumulatedDistance);
						
						if ( rate < _closedRate && _accumulatedDistance > _minLength ) {
							//Extract the loop
							var loop:Array = _points.slice(_points.indexOf(_first),_points.length);
							_loops.push(loop);
							
							_accumulatedPoint += loop.length;
							
							// add closed rate into the head
							loop.unshift(rate);
							trace("  Extracted Loop:(" + _first.x + "," + _first.y + ")" + _accumulatedDistance + ":" + _accumulatedPoint);
							//trace(_loops.length + ":" + _threshold + ":" + _extracted);
							_extracted = true;
						}
//					} else {
//						trace("  First detected loop");
					}
					resetLoop(_points[_points.length-1]);
				}
			}
			_last2 = _last1;
			_last1 = pt;
			_points.push(pt);
			
			//trace(_loops.length + ":" + _threshold + ":" + _extracted);
			if (_loops.length > _threshold && _extracted) {
				//trace("Ectracted event was fired.");
				dispatchEvent(new Event(EXTRACTED));
				_first = null;
				_extracted = false;
			}
			
			return theata;
		}
		public function pickUpTheBestLoop():Array {
			//Discard the first and last data.
			_loops.shift();
			_loops.pop();

			//Sort data
			_pointsAverage = _accumulatedPoint / _loops.length;
			_loops = _loops.sort(sortLoop);
			
			//Pick up the best data
			if (_loops[0] != null) {
				return _loops[0].slice(1);
			} else {
				return null;
			}
		}
		private function sortLoop(a:Array,b:Array):int {
			var rate_a:Number = a[0];
			var rate_b:Number = b[0];
			var distance_a:Number = (a.length - _pointsAverage)*(a.length - _pointsAverage);
			var distance_b:Number = (b.length - _pointsAverage)*(b.length - _pointsAverage);
			
			if (rate_a < rate_b) {
				return -1;
			} if (rate_a == rate_b) {
				if (distance_a < distance_b) {
					return -1;
				} else if (distance_a == distance_b) {
					return 0;
				}
			}
			return 1;
		}
		private function getLength(a:Point):Number {
			return Math.sqrt(a.x * a.x + a.y * a.y);
		}
		public function debug():void {
//			var first:Boolean = true;
//			var result:Array;
//			var min:Number = 100;
//			result = pickUpTheBestLoop();
//			if (result == null) {
//				return;
//			}
//			min = result.shift();
////
////			trace("\t\t\t\t// rate = "+min);
//			for each(var pt:Point in result) {
//				if (first) {
//					trace("\t\t\t\tcanvas.graphics.moveTo(" + pt.x + "," + pt.y + ");");
//				} else {
//					trace("\t\t\t\tcanvas.graphics.lineTo(" + pt.x + "," + pt.y + ");");
//				}
//				first = false;
//			}
		}
	}
}